/*
   Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
   Original sources are available at www.latestbit.com

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

#ifndef OBJECTPOOL_HPP
#define OBJECTPOOL_HPP

#include <set>
#include <queue>
#include "cpputils/CppUtils.hpp"
#include "cpputils/ept/ObjectPoolItemPtrFactory.hpp"


namespace cpputils {
	/**
	 * Effective Programming Templates (EPT)
	 * This namespace collects the programming templates
	 * for effeciently usage resources (CPU, Memory and others).
	 */
	namespace ept {

		/**
		 * Objects pool implementation
		 * Objects pool shares the object instances for more efficiently using memory management.
		 * Objects instance in pool are never recreated.
		 * @par Usage:
		 * @code
		 * // Creation a pool. Maximum of instances in the pool is 1000 and initially will be created is 5.
		 * ObjectPool<MyClass> pool = new ObjectPool<MyClass> ( 1000, 5 );
		 * // Get an instance from pool
		 * MyClass* instance = pool->getObject();
		 * // Return the instance to pool
		 * pool->returnObject(instance);
		 * @endcode
		 */
		template <typename T>
		class ObjectPool : public virtual RefObject {
			unsigned int maxObjectsCnt;
			unsigned int initObjectsCnt;
			typedef T* ItemPtr;
			typedef std::queue<ItemPtr> ItemSet;
			typedef std::set<ItemPtr> FreeItemSet;
			ItemSet items;
			FreeItemSet freeItems;
			IObjectPoolItemFactory<T>* factory;
			bool factoryAutoRelease;

			boost::mutex mtxItems;
			boost::condition evtDataAwait;
			bool exiting;

			void prepareInitObjects() {
				for(unsigned int i=0; i < this->initObjectsCnt; i++) {
					ItemPtr item ( createItem() );
					items.push ( item );
				}
			}
		protected:
			virtual ItemPtr createItem() {
				return factory->create();
			}
		public:
			/**
			 * Creation the pool
			 * @param maxObjectsCnt Maximum objects in the pool
			 * @param initObjectsCnt Initial count objects which creating immediately.
			 * @param factory An object instance creation factory. If not specified (param is NULL) - a default factory is created for usual pointers.
			 * @param autoReleaseFactory If this true, factory is released by ObjectPool in destructor
			 */
			inline ObjectPool(unsigned int maxObjectsCnt, unsigned int initObjectsCnt, IObjectPoolItemFactory<T>* factory = NULL, bool autoReleaseFactory=false) {
				this->maxObjectsCnt = maxObjectsCnt;
				this->initObjectsCnt = initObjectsCnt;
				exiting = false;
				this->factory = factory;
				if(!this->factory) {
					this->factory = new ObjectPoolItemPtrFactory<T>();
					this->factoryAutoRelease = true;
				}
				else
					this->factoryAutoRelease = autoReleaseFactory;
				prepareInitObjects();
			}

			inline virtual ~ObjectPool() {
				//items.c.clear();
				//freeItems.clear();
				boost::mutex::scoped_lock scopedLock(mtxItems);
				exiting = true;
				evtDataAwait.notify_all();
				while(!items.empty()) {
					ItemPtr ptr = items.front();
					factory->destroy ( ptr );
					items.pop();
				}

				foreach (ItemPtr ptr, freeItems) {
					factory->destroy ( ptr );
				}
				freeItems.clear();
				scopedLock.unlock();


				if(factoryAutoRelease) {
					delete factory;
					factory = NULL;
				}
			}

			/**
			 * Get the instance of object from pool
			 * @param await If this is true, then the user thread is awaiting for instance,
			 * if all exists instances are busy and pool is full.
			 * If false, then NULL will be returning.
			 */
			inline ItemPtr getObject(bool await=false) {
				boost::mutex::scoped_lock scopedLock(mtxItems);
				bool tryingToGet = false;
				do {
					if(!items.empty()) {
						ItemPtr result = items.front();
						items.pop();
						freeItems.insert(result);
						return result;
					}
					else {
						if(!isFull()) {
							ItemPtr result = createItem();
							freeItems.insert(result);
							return result;
						}
						else {
							if(await) {
								evtDataAwait.wait(scopedLock);
								if(!exiting)
									tryingToGet = true;
							}
						}
					}
				}
				while(tryingToGet);
				return NULL;
			}

			/**
			 * Return true if pool is full
			 */
			inline bool isFull() {
				return this->maxObjectsCnt < items.size()+freeItems.size() ;
			}

			/**
			 * Return an instance to pool
			 */
			inline void returnObject(ItemPtr pItem) {
				boost::mutex::scoped_lock scopedLock(mtxItems);
				typename FreeItemSet::iterator it = freeItems.find( pItem );
				if(it!=freeItems.end()) {
					ItemPtr item = (*it);
					freeItems.erase(it);
					if(!isFull()) {
						factory->reset(pItem);
						items.push ( item );
						evtDataAwait.notify_one();
					}
					else {
						factory->destroy (pItem);
					}
				}
			}

			typedef boost::shared_ptr < ObjectPool<T> > SharedPtr;
		};
	}
}

#endif // OBJECTPOOL_HPP
